﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Xml;
using Bridge.Base.Constructs;
using Bridge.Objects;
using Ionic.Zip;
using log4net;
using log4net.Config;

namespace Bridge.UI
{
    /// <summary>
    /// Interaction logic for Main.xaml
    /// </summary>
    public partial class Main : Window
    {
        public static EventHandler AssembliesReloaded;

        public static void OnAssembliesReloaded()
        {
            EventHandler eventHandler = AssembliesReloaded;

            if (eventHandler != null)
            {
                eventHandler(null, new EventArgs());
            }
        }

        public static bool LoadFile(string fileName)
        {
            FileInfo fileInfo = new FileInfo(fileName);
            if (fileInfo.Exists)
            {
                if (fileInfo.Extension.Equals(".bpkg", StringComparison.OrdinalIgnoreCase))
                {
                    DirectoryInfo directoryInfo = null;

                    try
                    {
                        directoryInfo = new DirectoryInfo(Path.Combine(Path.GetTempPath(), string.Format("bridgePackageTemp{0}", DateTime.Now.ToString("MMddyyyyhhmmss"))));
                        directoryInfo.Create();
                        using (ZipFile zipFile = new ZipFile(fileInfo.FullName))
                        {
                            zipFile.ExtractAll(directoryInfo.FullName, ExtractExistingFileAction.OverwriteSilently);
                        }
                        if (File.Exists(Path.Combine(directoryInfo.FullName, "Package.xml")))
                        {
                            using (FileStream xmlFileStream = File.OpenRead(Path.Combine(directoryInfo.FullName, "Package.xml")))
                            using (XmlReader xmlReader = XmlReader.Create(xmlFileStream))
                            {
                                string name = string.Empty;
                                string loadDLL = string.Empty;
                                while (xmlReader.Read())
                                {
                                    if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name.Equals("name", StringComparison.OrdinalIgnoreCase))
                                    {
                                        xmlReader.Read();
                                        name = xmlReader.Value;
                                    }
                                    else if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name.Equals("loadDLL", StringComparison.OrdinalIgnoreCase))
                                    {
                                        xmlReader.Read();
                                        loadDLL = xmlReader.Value;
                                    }
                                }
                                if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(loadDLL))
                                {
                                    DirectoryInfo dllExportDirectoryInfo = new DirectoryInfo(Path.Combine(Path.GetTempPath(), string.Format("Bridge.Package.{0}", name)));
                                    if (!dllExportDirectoryInfo.Exists)
                                    {
                                        dllExportDirectoryInfo.Create();
                                    }
                                    foreach (FileInfo copyFileInfo in directoryInfo.GetFiles())
                                    {
                                        copyFileInfo.CopyTo(Path.Combine(dllExportDirectoryInfo.FullName, Path.GetFileName(copyFileInfo.FullName)), true);
                                    }
                                    if (File.Exists(Path.Combine(dllExportDirectoryInfo.FullName, loadDLL)))
                                    {
                                        string dll = Path.Combine(dllExportDirectoryInfo.FullName, loadDLL);
                                        return LoadDLL(dll);
                                    }
                                }
                            }
                        }
                    }
                    finally
                    {
                        if (directoryInfo != null && directoryInfo.Exists)
                        {
                            directoryInfo.Delete(true);
                        }
                    }
                }
                else if (fileInfo.Extension.Equals(".dll", StringComparison.OrdinalIgnoreCase))
                {
                    LoadDLL(fileName);
                }
            }
            return false;
        }

        public static string ActiveTab
        {
            get
            {
                if (sActiveMain != null)
                {
                    return (sActiveMain.tabControlMainSwitchboard.SelectedItem as TabItem).Header.ToString();
                }
                return null;
            }
            set
            {
                if (sActiveMain != null)
                {
                    foreach (TabItem tabItem in sActiveMain.tabControlMainSwitchboard.Items)
                    {
                        if (tabItem.Header.ToString().Equals(value, StringComparison.OrdinalIgnoreCase))
                        {
                            tabItem.Focus();
                        }
                    }
                }
            }
        }

        private static Main sActiveMain;

        public Main()
        {
            XmlConfigurator.Configure();
            sLogInitialized = true;

            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
            this.Closing += new System.ComponentModel.CancelEventHandler(Main_Closing);

            InitializeComponent();
            LoadDLLs();

            mCharacters = new ObservableCollection<Character>();
            listBoxCharacters.ItemsSource = mCharacters;
            MetricsLog.Info("Loaded CharacterBridge");
            sActiveMain = this;

            if (Properties.Settings.Default.ShowWelcomeOnStartup)
            {
                WelcomeWindow welcomeWindow = new WelcomeWindow();
                welcomeWindow.Show();
                welcomeWindow.Focus();
            }
        }

        private void Main_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (sAlreadyClosing) return;

            if (WarningWindow.ShowDialog(WarningWindow.WarningType.YesNo, "Are you sure you want to close CharacterBridge?", null, "Close CharacterBridge?") == WarningWindow.WarningResult.No)
            {
                e.Cancel = true;
            }
        }

        private static ILog MetricsLog
        {
            get
            {
                if (sLogInitialized && sMetricsLog == null)
                {
                    sMetricsLog = LogManager.GetLogger("Bridge.Base.Utilities.Metrics");
                }
                else if (!sLogInitialized)
                {
                    return LogManager.GetLogger("Bridge.Base.Utilities.Metrics");
                }
                return sMetricsLog;
            }
        }

        private static ILog Log
        {
            get
            {
                if (sLogInitialized && sLog == null)
                {
                    sLog = LogManager.GetLogger("Bridge.UI.Main");
                }
                else if (!sLogInitialized)
                {
                    return LogManager.GetLogger("Bridge.UI.Main");
                }
                return sLog;
            }
        }

        private static bool sLogInitialized = false;
        private static ILog sLog;
        private static ILog sMetricsLog;
        private static bool sAlreadyClosing = false;

        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            if (e.ExceptionObject is Exception)
            {
                Log.Fatal("Unhandled Exception!", (Exception)e.ExceptionObject);
                WarningWindow.ShowWarning(WarningWindow.WarningType.OK, string.Format("Unhandled Exception! This is probably an issue inside the UI itself. Report this to Zachare Sylvestre/Imper1um.\nError Occurance: {0}", DateTime.Now), e.ExceptionObject.ToString(), "Unhandled Exception");
            }
        }

        private static bool LoadDLL(string dll)
        {
            try
            {
                Assembly.LoadFrom(dll);
                return true;
            }
            catch (BadImageFormatException ex)
            {
                Log.Warn(string.Format("LoadDLLs: Load {0} Failed.", dll), ex);
                WarningWindow.ShowWarning(WarningWindow.WarningType.OK, string.Format("Could not load DLL {0}. It is invalid. Check to make sure the file is a valid DLL file. Removing DLL from auto-load.", dll), ex.ToString());
            }
            catch (FileLoadException ex)
            {
                Log.Warn(string.Format("LoadDLLs: Load {0} Failed.", dll), ex);
                WarningWindow.ShowWarning(WarningWindow.WarningType.OK, string.Format("Could not load DLL {0}. It is invalid. Check to make sure the file is not corrupt. If you downloaded the file, try redownloading it. Removing DLL from auto-load", dll), ex.ToString());
            }
            return false;
        }

        private ObservableCollection<Character> mCharacters;

        private void LoadDLLs()
        {
            StringCollection dllPaths = Properties.Settings.Default.DLLPaths ?? new StringCollection();
            StringCollection newDllPaths = new StringCollection();
            foreach (string dll in dllPaths)
            {
                if (File.Exists(dll))
                {
                    if (!LoadFile(dll))
                    {
                        Log.Warn(string.Format("LoadDLLs: Load {0} Failed.", dll));
                        WarningWindow.ShowWarning(WarningWindow.WarningType.OK, string.Format("Could not load DLL {0}. It is invalid. Check to make sure the file is a valid DLL file. Removing DLL from auto-load.", dll));
                    }
                    else
                    {
                        newDllPaths.Add(dll);
                        Log.InfoFormat("LoadDLLs: Loaded {0}", dll);
                    }
                }
                else
                {
                    Log.WarnFormat("LoadDLLs: Unable to load DLL {0}. It does not exist.", dll);
                    WarningWindow.ShowWarning(WarningWindow.WarningType.OK, string.Format("Could not load DLL {0}. It does not exist. Removing DLL from auto-load.", dll));
                }
            }
            Properties.Settings.Default.DLLPaths = newDllPaths;
            Properties.Settings.Default.Save();
            EventHandler assembliesReloaded = AssembliesReloaded;

            if (assembliesReloaded != null)
            {
                assembliesReloaded(this, new EventArgs());
            }

            List<Type> types = new List<Type>();
            Type characterType = typeof(Character);
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (characterType.IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
                    {
                        types.Add(type);
                    }
                }
            }
            List<string> characterTypeNames = new List<string>();
            foreach (Type type in types)
            {
                Character character = (Character)type.GetConstructor(Type.EmptyTypes).Invoke(null);
                if (!characterTypeNames.Contains(character.CharacterType))
                {
                    characterTypeNames.Add(character.CharacterType);
                }
            }
            buttonCreateBlank.ContextMenu.Items.Clear();
            foreach (string characterTypeName in characterTypeNames)
            {
                MenuItem menuItem = new MenuItem() { Header = characterTypeName };
                menuItem.Click += new RoutedEventHandler(createBlankMenuItem_Click);
                buttonCreateBlank.ContextMenu.Items.Add(menuItem);
            }
        }

        private void createBlankMenuItem_Click(object sender, RoutedEventArgs e)
        {
            MenuItem menuItem = (MenuItem)sender;
            string characterTypeName = menuItem.Header.ToString();

            List<Type> types = new List<Type>();
            Type characterType = typeof(Character);
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (characterType.IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
                    {
                        types.Add(type);
                    }
                }
            }
            foreach (Type type in types)
            {
                Character character = (Character)type.GetConstructor(Type.EmptyTypes).Invoke(null);
                if (character.CharacterType.Equals(characterTypeName, StringComparison.OrdinalIgnoreCase))
                {
                    mCharacters.Add(character);
                    return;
                }
            }
        }

        private void ImportStatblock_CharacterParsed(object sender, RoutedEventArgs e)
        {
            Character character = (Character)e.OriginalSource;
            mCharacters.Add(character);
        }


        private void DLLManager_DLLLoaded(object sender, RoutedEventArgs e)
        {
            EventHandler eventHandler = AssembliesReloaded;

            if (eventHandler != null)
            {
                eventHandler(sender, e);
            }
        }

        private void listBoxCharacters_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            stackPanelEdit.Content = null;
            if (e.AddedItems.Count > 0)
            {
                Character character = (Character)e.AddedItems[0];
                if (character is IViewableCharacter)
                {
                    IViewableCharacter viewableCharacter = (IViewableCharacter)character;
                    CharacterView characterView = viewableCharacter.GetCharacterView();
                    UserControl userControl = characterView.EditCharacterUserControl;
                    stackPanelEdit.Content = userControl;
                }
                exportCharacter.CharacterSelected(character);
            }
        }

        private void TopCloseBar_Drag(object sender, RoutedEventArgs e)
        {
            DragMove();
        }

        private void TopCloseBar_Close(object sender, RoutedEventArgs e)
        {
            if (WarningWindow.ShowDialog(WarningWindow.WarningType.YesNo, "Are you sure you want to close CharacterBridge?", null, "Close CharacterBridge?") == WarningWindow.WarningResult.Yes)
            {
                sAlreadyClosing = true;
                Close();
            }
        }

        private void TopCloseBar_Minimize(object sender, RoutedEventArgs e)
        {
            WindowState = WindowState.Minimized;
        }

    }
}
